package be.dch.guice.utils;

import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import org.aopalliance.intercept.MethodInterceptor;
import org.apache.log4j.Logger;

import com.google.inject.AbstractModule;
import com.google.inject.Binder;
import com.google.inject.Key;
import com.google.inject.MembersInjector;
import com.google.inject.Module;
import com.google.inject.Provider;
import com.google.inject.Scope;
import com.google.inject.Stage;
import com.google.inject.TypeLiteral;
import com.google.inject.binder.AnnotatedBindingBuilder;
import com.google.inject.binder.AnnotatedConstantBindingBuilder;
import com.google.inject.binder.LinkedBindingBuilder;
import com.google.inject.matcher.Matcher;
import com.google.inject.spi.Message;
import com.google.inject.spi.TypeConverter;
import com.google.inject.spi.TypeListener;

@SuppressWarnings("unchecked")
public class AbstractModuleUtil {

    private static final Logger LOG = Logger.getLogger(AbstractModuleUtil.class);

    private final AbstractModule module;

    public AbstractModuleUtil(final AbstractModule module) {
	this.module = module;
    }

    /*
     * (non-Javadoc)
     * 
     * @see
     * com.google.inject.AbstractModule#addError(com.google.inject.spi.Message)
     */

    public void addError(final Message message) {
	this.invokeMethod("addError", message);
    }

    /*
     * (non-Javadoc)
     * 
     * @see com.google.inject.AbstractModule#addError(java.lang.String,
     * java.lang.Object[])
     */

    public void addError(final String message, final Object... arguments) {
	this.invokeMethod("addError", message, arguments);
    }

    /*
     * (non-Javadoc)
     * 
     * @see com.google.inject.AbstractModule#addError(java.lang.Throwable)
     */

    public void addError(final Throwable t) {
	this.invokeMethod("addError", t);
    }

    /*
     * (non-Javadoc)
     * 
     * @see com.google.inject.AbstractModule#bind(java.lang.Class)
     */

    public <T> AnnotatedBindingBuilder<T> bind(final Class<T> clazz) {
	return (AnnotatedBindingBuilder<T>) this.invokeMethod("bind", clazz);
    }

    /*
     * (non-Javadoc)
     * 
     * @see com.google.inject.AbstractModule#bind(com.google.inject.Key)
     */

    public <T> LinkedBindingBuilder<T> bind(final Key<T> key) {

	return (LinkedBindingBuilder<T>) this.invokeMethod("bind", key);
    }

    /*
     * (non-Javadoc)
     * 
     * @see com.google.inject.AbstractModule#bind(com.google.inject.TypeLiteral)
     */

    public <T> AnnotatedBindingBuilder<T> bind(final TypeLiteral<T> typeLiteral) {

	return (AnnotatedBindingBuilder<T>) this.invokeMethod("bind", typeLiteral);
    }

    /*
     * (non-Javadoc)
     * 
     * @see com.google.inject.AbstractModule#bindConstant()
     */

    public AnnotatedConstantBindingBuilder bindConstant() {
	return (AnnotatedConstantBindingBuilder) this.invokeMethod("bindConstant");
    }

    /*
     * (non-Javadoc)
     * 
     * @see
     * com.google.inject.AbstractModule#bindInterceptor(com.google.inject.matcher
     * .Matcher, com.google.inject.matcher.Matcher,
     * org.aopalliance.intercept.MethodInterceptor[])
     */

    public void bindInterceptor(final Matcher<? super Class<?>> classMatcher, final Matcher<? super Method> methodMatcher, final MethodInterceptor... interceptors) {
	this.invokeMethod("bindInterceptor", classMatcher, methodMatcher, interceptors);
    }

    /*
     * (non-Javadoc)
     * 
     * @see
     * com.google.inject.AbstractModule#bindListener(com.google.inject.matcher
     * .Matcher, com.google.inject.spi.TypeListener)
     */

    public void bindListener(final Matcher<? super TypeLiteral<?>> typeMatcher, final TypeListener listener) {

	this.invokeMethod("bindListener", typeMatcher, listener);
    }

    /*
     * (non-Javadoc)
     * 
     * @see com.google.inject.AbstractModule#bindScope(java.lang.Class,
     * com.google.inject.Scope)
     */

    public void bindScope(final Class<? extends Annotation> scopeAnnotation, final Scope scope) {

	this.invokeMethod("bindScope", scopeAnnotation, scope);
    }

    /*
     * (non-Javadoc)
     * 
     * @see com.google.inject.AbstractModule#binder()
     */

    public Binder binder() {
	return (Binder) this.invokeMethod("binder");
    }

    /*
     * (non-Javadoc)
     * 
     * @see
     * com.google.inject.AbstractModule#convertToTypes(com.google.inject.matcher
     * .Matcher, com.google.inject.spi.TypeConverter)
     */

    public void convertToTypes(final Matcher<? super TypeLiteral<?>> typeMatcher, final TypeConverter converter) {

	this.invokeMethod("convertToTypes", typeMatcher, converter);
    }

    /*
     * (non-Javadoc)
     * 
     * @see com.google.inject.AbstractModule#currentStage()
     */

    public Stage currentStage() {

	return (Stage) this.invokeMethod("currentStage");
    }

    /*
     * (non-Javadoc)
     * 
     * @see com.google.inject.AbstractModule#getMembersInjector(java.lang.Class)
     */

    public <T> MembersInjector<T> getMembersInjector(final Class<T> type) {

	return (MembersInjector<T>) this.invokeMethod("getMembersInjector", type);
    }

    /*
     * (non-Javadoc)
     * 
     * @see
     * com.google.inject.AbstractModule#getMembersInjector(com.google.inject
     * .TypeLiteral)
     */

    public <T> MembersInjector<T> getMembersInjector(final TypeLiteral<T> type) {

	return (MembersInjector<T>) this.invokeMethod("getMembersInjector", type);
    }

    /*
     * (non-Javadoc)
     * 
     * @see com.google.inject.AbstractModule#getProvider(java.lang.Class)
     */

    public <T> Provider<T> getProvider(final Class<T> type) {

	return (Provider<T>) this.invokeMethod("getProvider", type);
    }

    /*
     * (non-Javadoc)
     * 
     * @see com.google.inject.AbstractModule#getProvider(com.google.inject.Key)
     */

    public <T> Provider<T> getProvider(final Key<T> key) {

	return (Provider<T>) this.invokeMethod("getProvider", key);
    }

    /*
     * (non-Javadoc)
     * 
     * @see com.google.inject.AbstractModule#install(com.google.inject.Module)
     */

    public void install(final Module module) {

	this.invokeMethod("install", module);
    }

    /*
     * (non-Javadoc)
     * 
     * @see com.google.inject.AbstractModule#requestInjection(java.lang.Object)
     */

    public void requestInjection(final Object instance) {

	this.invokeMethod("requestInjection", instance);
    }

    /*
     * (non-Javadoc)
     * 
     * @see
     * com.google.inject.AbstractModule#requestStaticInjection(java.lang.Class
     * <?>[])
     */

    public void requestStaticInjection(final Class<?>... types) {
	this.invokeMethod("requestStaticInjection", (Object[]) types);
    }

    /*
     * (non-Javadoc)
     * 
     * @see com.google.inject.AbstractModule#requireBinding(java.lang.Class)
     */

    public void requireBinding(final Class<?> type) {

	this.invokeMethod("requireBinding", type);
    }

    /*
     * (non-Javadoc)
     * 
     * @see
     * com.google.inject.AbstractModule#requireBinding(com.google.inject.Key)
     */

    public void requireBinding(final Key<?> key) {
	this.invokeMethod("requireBinding", key);
    }

    private Object invokeMethod(final String methodName, final Object... arguments) {
	Method m;
	try {
	    m = this.module.getClass().getSuperclass().getDeclaredMethod(methodName);
	    m.setAccessible(true);
	    return m.invoke(this.module, arguments);
	} catch (final SecurityException e) {
	    AbstractModuleUtil.LOG.error(e);
	} catch (final NoSuchMethodException e) {
	    AbstractModuleUtil.LOG.error(e);
	} catch (final IllegalArgumentException e) {
	    AbstractModuleUtil.LOG.error(e);
	} catch (final IllegalAccessException e) {
	    AbstractModuleUtil.LOG.error(e);
	} catch (final InvocationTargetException e) {
	    AbstractModuleUtil.LOG.error(e);
	}
	return null;

    }
}
